Conversation
There was a problem hiding this comment.
Code Review
This pull request implements a new full-text search feature, including a dedicated /search route, a debounced SearchInput component, and necessary updates to the entry list fetching logic to support query parameters. The review identified issues regarding redundant keyboard shortcut management, side effects during the render phase in the SearchInput component, and the need for immediate URL updates when clearing the search input.
| const urlQuery = searchParams?.get("q") ?? ""; | ||
| const [inputValue, setInputValue] = useState(urlQuery); | ||
| const inputRef = useRef<HTMLInputElement>(null); | ||
| const { setEnabled: setKeyboardShortcutsEnabled } = useKeyboardShortcutsContext(); |
There was a problem hiding this comment.
The manual toggling of keyboard shortcuts is redundant. The useKeyboardShortcuts hook in this project already uses enableOnFormTags: false for all its hotkeys, which automatically disables them when an input or search field is focused. Removing this manual state management simplifies the component.
| const { setEnabled: setKeyboardShortcutsEnabled } = useKeyboardShortcutsContext(); | |
| const inputRef = useRef<HTMLInputElement>(null); |
| if (urlQuery !== lastWrittenRef.current) { | ||
| lastWrittenRef.current = urlQuery; | ||
| if (urlQuery !== inputValue) { | ||
| setInputValue(urlQuery); | ||
| } | ||
| } |
There was a problem hiding this comment.
Mutating a ref during the render phase is a side effect that violates React's rules. This can lead to unpredictable behavior in concurrent rendering. Instead, use the recommended pattern for adjusting state when a prop (or URL parameter) changes by tracking the previous value in state.
| if (urlQuery !== lastWrittenRef.current) { | |
| lastWrittenRef.current = urlQuery; | |
| if (urlQuery !== inputValue) { | |
| setInputValue(urlQuery); | |
| } | |
| } | |
| const [prevUrlQuery, setPrevUrlQuery] = useState(urlQuery); | |
| if (urlQuery !== prevUrlQuery) { | |
| setPrevUrlQuery(urlQuery); | |
| setInputValue(urlQuery); | |
| } |
| const handleClear = useCallback(() => { | ||
| setInputValue(""); | ||
| inputRef.current?.focus(); | ||
| }, []); |
There was a problem hiding this comment.
The clear action should update the URL immediately to provide instant feedback to the user. Currently, it relies on the 300ms debounce timer, which causes the previous search results to linger after the input is cleared.
const handleClear = useCallback(() => {
setInputValue("");
const params = new URLSearchParams(searchParams?.toString() ?? "");
params.delete("q");
const queryString = params.toString();
const url = queryString ? "/search?" + queryString : "/search";
clientReplace(url);
inputRef.current?.focus();
}, [searchParams]);
b470d95 to
bad2374
Compare
Adds a /search page that exposes the existing backend full-text search (entries.list query parameter) in the UI. The search input debounces user input and updates the URL's ?q= parameter, which drives the PostgreSQL full-text search with relevance ranking. Features: - Search page at /search with autofocusing search input - Search icon button in the header for quick access - "/" keyboard shortcut navigates to search (like Gmail/GitHub) - Debounced input (300ms) updates URL, triggering new search - Results ranked by relevance via PostgreSQL ts_rank - "Type a search query" prompt when no query entered - Clear button to reset search - Keyboard shortcuts disabled while search input is focused - Search documented in keyboard shortcuts modal (? key) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bad2374 to
d8d6b5d
Compare
Summary
/searchpage with a debounced search input that leverages the existing backend full-text search (entries.listquery parameter with PostgreSQLts_rankrelevance ranking)/keyboard shortcut for quick access (like Gmail/GitHub)Closes #565
Details
The backend already supported full-text search via the
entries.listendpoint'squeryparameter (using PostgreSQLto_tsvector/plainto_tsquery), but it was only exposed through the MCP server. This PR adds the frontend UI.Architecture
queryparameter added toEntriesListInputandEntriesListFiltersso it flows through the existing cache key infrastructureuseEntriesListInputreads the?q=URL parameter on the/searchpageSearchInputcomponent uses a controlled input with 300ms debounce, updating the URL viaclientReplaceunreadOnly: falseand hides sort toggle (results ranked by relevance)Files changed
entries-list-input.ts- Addedqueryto input/filter interfacesuseEntriesListInput.ts- Reads?q=from URL search paramsEntryListPage.tsx- Passesqparam for server-side prefetchingUnifiedEntriesContent.tsx- Added/searchroute with SearchInput title slotAppLayoutContent.tsx- Search icon in headeruseKeyboardShortcuts.ts-/shortcut navigates to searchKeyboardShortcutsModal.tsx- Documents/shortcutsearch/page.tsx,SearchInput.tsx,SearchIconTest plan
/searchvia header icon — search input autofocuses/key from any entry list — navigates to search page?— keyboard shortcuts modal shows/for search🤖 Generated with Claude Code